/**
 * (c) E.S.A.
 */


/**
 *  Functions for connection with a server (Socket Engine)
 */

#include "connect_utilities.h"
#include "global_data.h"
using namespace std;
#include <iostream>

#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>

global_s global;

/*
 *  Creates reusable ipv4 socket 
 */
int create_socket ()
{
	int sock = socket(PF_INET, SOCK_STREAM, 0);
	if(sock < 0){
		cout << "Error: could not create socket" << endl;
		return sock;
	}

	int optval = 1;
	if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0){
		cout << "Warning: could not set socket reuse option" << endl;
	}

	return sock;
}


/** 
 * CLIENT_MODE: Connect the client socket to a specific remote address 
 */
int connect_to_server (int sock, const std::string &remote_ip, int remote_port) 
{ 
	struct sockaddr_in serv_addr;

	bzero (&serv_addr, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(remote_port);
	inet_pton (AF_INET, remote_ip.c_str(), &serv_addr.sin_addr);

	int rez = connect (sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
	if (rez < 0) {
		cout << "Error: connect on socket failed" << endl;
		return -1;
	}


	return sock;
} 

/*
 *  Send data on socket
 */
int send_data_on_socket(int sock, const void* data, int len)
{

	const char* data_char = reinterpret_cast<const char*>(data);
	ssize_t totally_sent = 0, partially_sent = 0;

	while (totally_sent < len){

		partially_sent = send(sock, data_char + totally_sent, len - totally_sent, 0);
		if (partially_sent < 0){
			cout << "Error: data send failed" << endl;
			return -1;
		}

		totally_sent += partially_sent;

	}

	return 0;
}

/*
 *  Sends on the socket the message size 
 *  and afterwards the string 
 */
int send_data_len_prefixed (int sock, const string& message)
{
	uint32_t len = message.length();
	int err;

	cout << "Writing data to SocketEngine server.. " << endl;
	cout << "Data: \n" << message << "\n" << endl;
	/* Send data size */
	uint32_t nbo_len = htonl(len); //network byte order length
	err = send_data_on_socket(sock, &nbo_len, sizeof(nbo_len));
	if (err < 0)
		return -1;

	/* Send the message */
	err = send_data_on_socket(sock, message.c_str(), len);
	if(err < 0)
		return -1;

	return 0;
}

/*
 *  Receive data on socket
 */
int receive_data_on_socket (int sock, void* data, int len)
{
	ssize_t totally_read = 0, partially_read = 0;

	while (totally_read < len){
		partially_read = recv(sock, (char*)data + totally_read, len - totally_read, 0);
		if (partially_read < 0){
			cout << "Error: data recv failed" << endl;
			return -1;
		}
		if (partially_read == 0){
			cout << "Warning: connection lost" << endl;
			return 0;
		}

		totally_read += partially_read;

	}

	return 0;
}



void* connect_main(void* arg)
{ 
	cout << "connect thread starting ..." << endl; fflush(stdout);

	srv_data* server = static_cast<srv_data*>(arg); // ip, port

	bool read_size_flag = true; 
	int select_rez, len; 
	uint32_t nbo_mess_size, mess_size = 0;
	int sock;

	fd_set readfds, writefds, // exceptfds,
	tmp_readfds, tmp_writefds;
	struct timeval	timeout;

	char rcv_mess[MESS_SIZE];

	// connect to server: 
	sock = create_socket ();
	int rez = connect_to_server (sock, server->ip, server->port);
	if (rez == -1)
		return NULL;

	// add the file descriptor sock to the read/write/except set  
	{
		FD_ZERO(&readfds);
		FD_SET(sock, &readfds);

		FD_ZERO(&writefds);
		FD_SET(sock, &writefds);

		//FD_ZERO(&exceptfds);
		//FD_SET(sock, &exceptfds);
	}

	timeout.tv_sec = 1;
	timeout.tv_usec = 0;

	while (!global.quit_flag) {
		tmp_readfds = readfds;
		tmp_writefds = writefds;

		select_rez = select(sock+1, &tmp_readfds, &tmp_writefds, NULL, &timeout); 
		if (select_rez < 0){
			cout << "Error on select" << endl;
			goto c_m_thd_end;
		}
		if (select_rez == 0){
			cout << "No fds are ready \n" << endl;
		}

		if (FD_ISSET(sock, &tmp_readfds)) {
			cout << "is time to read from sock" << endl;

			if (read_size_flag == true){

				// read message size				
				len = sizeof(mess_size);
				int rez = 
					receive_data_on_socket (sock, &nbo_mess_size, len);
				if (rez != 0) break;

				if(len == 0){
					cout << "The connection with the other end was clossed.. " << endl;
					break;
				}
				mess_size = ntohl(nbo_mess_size);
				read_size_flag = false;
			} 
			else { 

				// receive data from server
				bzero (rcv_mess, MESS_SIZE * sizeof(char));
				len = mess_size;

				int rez = 
					receive_data_on_socket (sock, rcv_mess, len);
				if (rez != 0) {
					break;
				}

				if(len == 0){
					cout << "The connection with the other end was clossed.. " << endl;
					break;
				}


				cout << "MUST read " << mess_size << 
				" read  " << len << endl; 

				////////////////  receive data from REMOTE SERVER (@p) ///////////////////
				sem_wait( &global.recv_buff.empty_sem );
				sem_wait( &global.recv_buff.put_mutex );

				global.recv_buff.mess[global.recv_buff.nextin] = rcv_mess;
				global.recv_buff.nextin = (global.recv_buff.nextin + 1) % CIRCULAR_BUF_SIZE;

				sem_post( &global.recv_buff.put_mutex );
				sem_post( &global.recv_buff.full_sem );

				read_size_flag = true;

			} 
		}// end READ

		if (FD_ISSET(sock, &tmp_writefds)) {

			if ( sem_trywait( &global.send_buff.full_sem ) == 0 ) { 
				sem_wait( &global.send_buff.get_mutex);
	
				// send message on socket
				send_data_len_prefixed (sock, global.send_buff.mess[global.send_buff.nextout]);
				global.send_buff.nextout = (global.send_buff.nextout + 1) % CIRCULAR_BUF_SIZE;

				sem_post ( &global.send_buff.get_mutex);
				sem_post ( &global.send_buff.empty_sem);
			}

		} // end WRITE


	}

	c_m_thd_end:
	cout << "client mode thread finishing .. " << endl;

	return NULL;
}
